﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Planning.DataFace;
using Noris.Schedule.Planning.ProcessData;

namespace Noris.Schedule.Planning.ProcessData
{
	#region CLASS PlanningControlCls : Parametry plánovacího procesu, jsou přístupné ve všech provozních objektech
	/// <summary>
	/// Parametry plánovacího procesu, jsou přístupné ve všech provozních objektech.
	/// Obsahuje jak vstupní parametry (konfiguraci procesu), tak výstupní hodnoty (aplikační auditlog = zprávy o problémech).
	/// </summary>
	public class PlanningParametersCls
	{
		#region KONSTRUKCE, SINGLETON, LOCK
		/// <summary>
		/// Statická úschovna pro právě aktuální objekt řídících parametrů procesu.
        /// Hodnotu do něj ukládá metoda, která aktivuje verzi plánu.
		/// </summary>
		public static PlanningParametersCls Current
		{
			get
			{
				if (_Current == null)
				{
					lock (_WriteLock)
					{
						if (_Current == null)
							_Current = Default;
					}
				}
				return _Current;
			}
			set
			{
				lock (_WriteLock)
				{
					_Current = value;
				}
			}
		}
		private static PlanningParametersCls _Current;
		private static object _WriteLock = new object();
        private PlanningParametersCls()
        {
            this.InitialDateTime = DateTime.Now;
            this.ThreadMode = PlanningProcessThreadMode.MultiThreadOnMultiCore;
            this.EnableProposalCumulating = true;
        }
        #endregion
		#region DEFAULTY
		/// <summary>
		/// Obsahuje (vrací) novou instanci řídícího objektu PlanningControlCls,
		/// který obsahuje základní defaultní nastavení pro běh.
		/// </summary>
		public static PlanningParametersCls Default
		{
			get
			{
				PlanningParametersCls planningParameters = new PlanningParametersCls();
                planningParameters._PlanCVersionData = PlanCVersionHdrCls.Default;
				return planningParameters;
			}
		}
		/// <summary>
		/// Obsahuje (vrací) novou instanci řídícího objektu PlanningControlCls, 
		/// který obsahuje základní defaultní nastavení pro běh nad připojenou databází.
		/// </summary>
		public static PlanningParametersCls DefaultForRun
		{
			get
			{
                PlanningParametersCls planningParameters = new PlanningParametersCls();
                planningParameters._PlanCVersionData = PlanCVersionHdrCls.DefaultForRun;
                return planningParameters;
			}
		}
		/// <summary>
		/// Obsahuje (vrací) novou instanci řídícího objektu PlanningControlCls, který obsahuje základní defaultní nastavení pro testy.
		/// </summary>
		public static PlanningParametersCls DefaultForTest
		{
			get
			{
                PlanningParametersCls planningParameters = new PlanningParametersCls();
                planningParameters.InitialDateTime = new DateTime(2010, 1, 1, 12, 0, 0);
                planningParameters._PlanCVersionData = PlanCVersionHdrCls.DefaultForTest;
                return planningParameters;
			}
		}
        /// <summary>
        /// Nastaví dané hodnoty do parametrů procesu, pro testovací účely
        /// </summary>
        /// <param name="timing"></param>
        /// <param name="period"></param>
        /// <param name="minimumStockQtyMode"></param>
        internal void SetTestParams(MrpStructureTimingType timing, int period, MinimumStockQtyMode minimumStockQtyMode)
        {
            this._PlanCVersionData.SetTestParams(timing, period, minimumStockQtyMode);
        }
        #endregion
		#region PLÁNOVACÍ PROCES
		/// <summary> { get; set; }
		/// Verze kapacitního plánu, klíč = číslo subjektu
		/// </summary>
		public int PlanCVersion { get { return (_PlanCVersionData == null ? 0 : _PlanCVersionData.RecordNumber); } }
		/// <summary> { get; set; }
		/// Verze kapacitního plánu, data = celý záznam třídy PlanCVersionHdrCls.
		/// Jde o verzi, která je aktuálně platná.
		/// </summary>
		public PlanCVersionHdrCls PlanCVersionData { get { return _PlanCVersionData; } internal set { _PlanCVersionData = value; } }
		private PlanCVersionHdrCls _PlanCVersionData;
		/// <summary> { get; set; }
		/// Verze kapacitního plánu, na kterou se odvolává aktuální požadavek.
		/// Tj. nejde o verzi plánu aktuálně platnou, ale je dotazovanou.
		/// </summary>
		public PlanCVersionHdrCls RequestPlanCVersionData { get { return _RequestPlanCVersionData; } internal set { _RequestPlanCVersionData = value; } }
		private PlanCVersionHdrCls _RequestPlanCVersionData;
        /// <summary> { get; set; }
        /// Výchozí datum systému, datum včetně aktuálního času spuštění.
        /// Před toto datum nebude registrovat kapacity.
        /// </summary>
        public DateTime InitialDateTime { get { return _InitialDateTime; } internal set { _InitialDateTime = value; } }
        private DateTime _InitialDateTime;
        /// <summary> { get; }
		/// Výchozí datum systému, datum bez času.
        /// Před toto datum nebude registrovat kapacity.
		/// </summary>
        public DateTime InitialDate { get { return _InitialDateTime.Date; } }
        /// <summary> { get; }
        /// Výchozí datum pro vytvoření návrhu na minimání zásobu ke dni spuštění procesu (InitialDateTime) + přídavek MrpMinQtyDays
        /// </summary>
        public DateTime InitialDateMinStockProposal { get { return (this.MrpMinQtyDays <= 0d ? this.InitialDateTime : this.InitialDateTime.AddDays(this.MrpMinQtyDays)); } }
		/// <summary> { get; set; }
		/// Řízení threadingu.
		/// Default = MultiThreadOnMultiCore
		/// </summary>
		public PlanningProcessThreadMode ThreadMode { get { return _ThreadMode; } internal set { _ThreadMode = value; } }
		private PlanningProcessThreadMode _ThreadMode = PlanningProcessThreadMode.MultiThreadOnMultiCore;
		#endregion
        #region Dispečer stavu plánovacího procesu pro tuto verzi
        /// <summary>
        /// Nastaví požadovaný stav plánovacího procesu, včetně souvisejících akcí.
        /// Pokud stav nelze nastavit, vyhodí chybu.
        /// </summary>
        /// <param name="state"></param>
        internal void SetProcessState(PlanningProcessState state)
        {
            // Potřebujeme mít spolehlivě načtená a zamčená data o současném stavu plánu z databáze do this, abychom mohli reálně posoudit přípustnost změny.
            string errorInfo = "";
            using (var trans = DataConnect.CreateTransaction())
            {
                // Následující akce povede buď k zápisu do databáze, nebo k chybě.
                // Implementace mapy aktivit (obdoba Workflow), zde jsou implementována pravidla změn:
                // Pokud jednotlivé metody zjistí nepovolenou kombinaci stavů (z => do), pak vyhodí chybu.
                // Pokud metody nemají námitek, nemusí nic dělat a prostě skončí svůj běh (return).
                // Zdejší metoda poté provede vložení stavu state do this.CurrentProcessState, kde v set accessoru se nastaví i navázané hodnoty, a uložení do databáze.
                PlanCVersionHdrCls currentDbData;
                errorInfo = this._ChangeProcessStateGetInfo(state, true, out currentDbData);
                if (String.IsNullOrEmpty(errorInfo))
                {   // Změnu stavu provedu uvnitř transakce:
                    this._StorePrevEdState(state, currentDbData);
                    this.CurrentProcessState = state;
                }
                trans.Commit();
            }
            // Chybu ohlásím až po konci transakce:
            if (!String.IsNullOrEmpty(errorInfo))
                Throw.AplError(errorInfo);
        }
        /// <summary>
        /// Mohu přejít na daný stav?
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        internal bool CanSetProcessState(PlanningProcessState state)
        {
            string errorInfo;
            return this.CanSetProcessState(state, out errorInfo);
        }
        /// <summary>
        /// Mohu přejít na daný stav?
        /// Pokud ne, proč?
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        internal bool CanSetProcessState(PlanningProcessState state, out string errorInfo)
        {
            errorInfo = null;

            // Pokud u nás nedochází ke změně, velice rychle nic neděláme:
            PlanningProcessState currState = this.CurrentProcessState;
            if (state == currState) return true;

            errorInfo = this._ChangeProcessStateGetInfo(state, false);
            return (String.IsNullOrEmpty(errorInfo));
        }
        /// <summary>
        /// Zruší příznak rozpracovanosti (nebo ukládání dat), pokud tam je.
        /// </summary>
        internal void DisconnectProcessState()
        {
            if (!(this.CurrentProcessState == PlanningProcessState.NowEditing || this.CurrentProcessState == PlanningProcessState.SavingData))
                // Pokud nyní právě neprobíhá živá práce, skončím:
                return;

            this.PlanCVersionData.DirectSetWorkingState(this.PreEdDbProcessState, this.PreEdDbWorkingUser, this.PreEdDbWorkingTime);
        }
        /// <summary>
        /// Uloží do svých stavových proměnných informace potřebné pro metodu DisconnectProcessState().
        /// Jde o opis DB hodnot Work, a to při vstupu do stavu NowEditing (nebo přímo NowSaving).
        /// Důležité jsou pro případ, kdy se opouští rozdělaná práce a máme do databáze do Work položek vrátit původní stav.
        /// </summary>
        /// <param name="newState"></param>
        /// <param name="currentDbData"></param>
        private void _StorePrevEdState(PlanningProcessState newState, PlanCVersionHdrCls currentDbData)
        {
            PlanningProcessState currState = this.CurrentProcessState;
            bool currEdit = (currState == PlanningProcessState.NowEditing || currState == PlanningProcessState.SavingData);
            bool newEdit = (newState == PlanningProcessState.NowEditing || newState == PlanningProcessState.SavingData);

            if (!currEdit && newEdit)
            {   // Dosud needituji, ale právě začínám => do PreEd si uložím hodnoty z databáze:
                this.PreEdDbProcessState = currentDbData.CurrentProcessState;
                this.PreEdDbWorkingUser = currentDbData.WorkingUser;
                this.PreEdDbWorkingTime = currentDbData.WorkingTime;
            }
            else if (currEdit && !newEdit)
            {   // Dosud jsem editoval, ale právě končím => do PreEd si uložím None:
                this.PreEdDbProcessState = PlanningProcessState.None;
                this.PreEdDbWorkingUser = SqlInt32.Null;
                this.PreEdDbWorkingTime = SqlDateTime.Null;
            }
        }
        /// <summary>
        /// Aktuální stav plánovacího procesu.
        /// Tato property v set metodě nemá žádnou logiku typu Dispečer !!!
        /// Setování hodnoty doplní hodnoty do working_user a do working_time, a okamžitě data ukládá do databáze.
        /// V případě změny stavu na stav AfterSave, nebo ze stavu SavingData na stav Save uloží i hodnoty do last_plan_user a last_plan_time, a zapíše je do databáze.
        /// Logiku typu Dispečer obsahuje metoda this.SetProcessState()
        /// </summary>
        public PlanningProcessState CurrentProcessState
        {
            get { return this.PlanCVersionData.CurrentProcessState; }
            private set { this.PlanCVersionData.CurrentProcessState = value; }
        }
        /// <summary>
        /// Zjistí, zda ze současného stavu objektu a dat v databázi lze přejít do nového stavu daného parametrem state.
        /// Uvnitř této metody se načítá z databáze aktuální stav záznamu odpovídajícího záznamu this, pro porovnání stavu.
        /// Vrací prázdný string (ano, lze přejít) nebo text popisující problém. Ten pak bude vydán jako chybová hláška.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="lockRecord"></param>
        /// <returns></returns>
        private string _ChangeProcessStateGetInfo(PlanningProcessState state, bool lockRecord)
        {
            PlanCVersionHdrCls currentDbData;
            return _ChangeProcessStateGetInfo(state, lockRecord, out currentDbData);
        }
        /// <summary>
        /// Zjistí, zda ze současného stavu objektu a dat v databázi lze přejít do nového stavu daného parametrem state.
        /// Uvnitř této metody se načítá z databáze aktuální stav záznamu odpovídajícího záznamu this, pro porovnání stavu.
        /// Vrací prázdný string (ano, lze přejít) nebo text popisující problém. Ten pak bude vydán jako chybová hláška.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="lockRecord"></param>
        /// <returns></returns>
        private string _ChangeProcessStateGetInfo(PlanningProcessState state, bool lockRecord, out PlanCVersionHdrCls currentDbData)
        {
            currentDbData = this.LoadCurrentData(lockRecord);
            switch (state)
            {
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                    return this._SetProcessStateToNone(currentDbData);
                case PlanningProcessState.PreEdit:
                    return this._SetProcessStateToPreEdit(currentDbData);
                case PlanningProcessState.NowEditing:
                    return this._SetProcessStateToNowEditing(currentDbData);
                case PlanningProcessState.SavingData:
                    return this._SetProcessStateToSavingData(currentDbData);
                case PlanningProcessState.AfterSave:
                    return this._SetProcessStateToAfterSave(currentDbData);
                case PlanningProcessState.Save:
                    return this._SetProcessStateToSave(currentDbData);
            }
            return "";
        }
        /// <summary>
        /// Provede změnu stavu procesu do stavu None nebo NotExist
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToNone(PlanCVersionHdrCls currentDbData)
        {
            // Do stavu None mohu přejít kdykoliv:
            return "";
        }
        /// <summary>
        /// Provede změnu stavu procesu do stavu PreEdit
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToPreEdit(PlanCVersionHdrCls currentDbData)
        {
            // PreEdit = stav, kdy uživatel data needituje, ale mohl by začít:
            // Do tohoto stavu mohu přejít kdykoliv:
            return "";
        }
        /// <summary>
        /// Provede změnu stavu procesu do stavu NowEditing
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToNowEditing(PlanCVersionHdrCls currentDbData)
        {
            // Zahájení editace:

            // Z některých stavů nelze začít editovat vůbec - ze stavu SavingData, z některých mohu bez dalších testů:
            PlanningProcessState currMemState = this.CurrentProcessState;
            switch (currMemState)
            {   // Přecházím do stavu NowEditing z daného stavu (z instance v paměti):
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                case PlanningProcessState.PreEdit:
                    break;
                case PlanningProcessState.NowEditing:
                    return "";
                case PlanningProcessState.SavingData:
                    return "Nelze zahájit editaci v situaci, kdy se ukládají data.";
                case PlanningProcessState.AfterSave:
                case PlanningProcessState.Save:
                    break;
            }

            // Pro zbývající situace musím ověřit, zda mohu zahájit editaci : zahájit ji může jen první uživatel, ostatní ne:
            PlanningProcessState currDbState = currentDbData.CurrentProcessState;
            if (currDbState == PlanningProcessState.NowEditing || currDbState == PlanningProcessState.SavingData)
            {   // Někdo edituje / ukládá data
                if (currentDbData.WorkingUser.IsNull)
                    // Nenapsal své jméno: budu ho ignorovat:
                    return "";
                if (!currentDbData.WorkingUser.IsNull && currentDbData.WorkingUser.Value == Steward.CurrentUser.Osoba)
                    // ... a jsem to já (?) :
                    return "";
                // Někdo edituje / ukládá data, a já to nejsem:
                return "Nelze zahájit editaci: " + PlanCVersionHdrCls.GetDbWorkingInfo(currentDbData) + ".";
            }

            if (currDbState == PlanningProcessState.AfterSave || currDbState == PlanningProcessState.Save)
            {   // Data v databázi jsou uložena:
                // a) pokud my nemáme načtená žádná data, značí to že tuto metodu voláme před načítáním dat, a pak není problém v konfliktu dat "v paměti" a "v databázi":
                //     anebo pokud v databázi není nic uloženého:
                if (this.TimeStampLastPlanUser.IsNull || this.TimeStampLastPlanTime.IsNull)
                    return "";

                // b) pokud v databázi jsou shodná data, jaká máme načtena v naší verzi plánu, pak je to OK:
                if (this.IsCorrectTimeStamp(currentDbData))
                    return "";
                return "Nelze zahájit editaci: " + PlanCVersionHdrCls.GetDbWorkingInfo(currentDbData) + ". Data je nutno znovu načíst.";
            }
            return "";
        }
        /// <summary>
        /// Provede změnu stavu procesu do stavu SavingData
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToSavingData(PlanCVersionHdrCls currentDbData)
        {
            // Zahájení ukládání dat:
            PlanningProcessState currMemState = this.CurrentProcessState;
            switch (currMemState)
            {   // Přecházím do stavu SavingData z daného stavu (z instance v paměti):
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                case PlanningProcessState.PreEdit:
                case PlanningProcessState.NowEditing:
                    break;
                case PlanningProcessState.SavingData:
                    return "Nelze zahájit editaci v situaci, kdy se již ukládají data.";
                case PlanningProcessState.AfterSave:
                case PlanningProcessState.Save:
                    break;
            }

            // Před ukládáním dat ověřím, zda do databáze někdo nevepsal příznak, že data edituje nebo právě ukládá:
            PlanningProcessState currDbState = currentDbData.CurrentProcessState;
            if (currDbState == PlanningProcessState.NowEditing || currDbState == PlanningProcessState.SavingData)
            {
                if (!currentDbData.WorkingUser.IsNull && currentDbData.WorkingUser.Value != Steward.CurrentUser.Osoba)
                    return "Nelze ukládat data: " + PlanCVersionHdrCls.GetDbWorkingInfo(currentDbData) + ". Došlo ke konfliktu.";
            }
            if (currDbState == PlanningProcessState.AfterSave || currDbState == PlanningProcessState.Save)
            {   // Data v databázi jsou uložena, pokud to jsou shodná data jako v naší verzi plánu pak je to OK:
                if (currentDbData.WorkingUser == this.PlanCVersionData.WorkingUser)
                    return "";
                return "Nelze ukládat data: " + PlanCVersionHdrCls.GetDbWorkingInfo(currentDbData) + ". Data je nutno znovu načíst.";
            }

            // Před ukládáním dat ověřím, zda stav dat v databázi se nezměnil od stavu, který máme v paměti:
            if (!this.IsCorrectTimeStamp(currentDbData))
            {
                return "Nelze ukládat data: " + PlanCVersionHdrCls.GetDbLastPlanInfo(currentDbData) + ". Došlo ke konfliktu.";
            }

            return "";
        }
        /// <summary>
        /// Provede změnu stavu procesu do stavu AfterSave
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToAfterSave(PlanCVersionHdrCls currentDbData)
        {
            PlanningProcessState currMemState = this.CurrentProcessState;
            switch (currMemState)
            {   // Přecházím do stavu AfterSave z daného stavu (z instance v paměti):
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                case PlanningProcessState.PreEdit:
                case PlanningProcessState.NowEditing:
                    break;
                case PlanningProcessState.SavingData:
                case PlanningProcessState.AfterSave:
                    return "";
                case PlanningProcessState.Save:
                    break;
            }
            return "Nelze přejít do stavu Data uložena (AfterSave) ze stavu " + currMemState.ToString();
        }
        /// <summary>
        /// Provede změnu stavu procesu ze stavu Save
        /// na daný stav.
        /// </summary>
        /// <param name="state"></param>
        private string _SetProcessStateToSave(PlanCVersionHdrCls currentDbData)
        {
            PlanningProcessState currMemState = this.CurrentProcessState;
            switch (currMemState)
            {   // Přecházím do stavu Save z daného stavu (z instance v paměti):
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                    break;
                case PlanningProcessState.PreEdit:
                case PlanningProcessState.NowEditing:
                case PlanningProcessState.SavingData:
                case PlanningProcessState.AfterSave:
                case PlanningProcessState.Save:
                    return "";
            }
            return "Nelze přejít do stavu Data uložena (Save) ze stavu " + currMemState.ToString();
        }
        /// <summary>
        /// Vrátí nový objekt obsahující aktuální data záznamu z databáze, ze stejné věty jako je this.
        /// Jenže this odráží náš soukromý stav, v něm se mění stav podle zdejších algoritmů, ale výstup této metody obsahuje aktuální obraz dat v databázi.
        /// Načítaný záznam je možno zamknout, abychom mohli vyhodnotit případné změny a konzistentně je i uložit bez problémů konkurenčního přístupu.
        /// </summary>
        /// <param name="lockRecord"></param>
        /// <returns></returns>
        private PlanCVersionHdrCls LoadCurrentData(bool lockRecord)
        {
            string sql = PlanCVersionHdrCls.SqlSelect + (lockRecord ? " with (rowlock, holdlock)" : " with (readuncommitted)") + " WHERE cislo_subjektu = " + SqlFormat.ToSql(this.PlanCVersionData.RecordNumber);
            return Db_Layer.GetRecord<PlanCVersionHdrCls>(sql);
        }
        /// <summary>
        /// Voláno po načtení / uložení dat z databáze. Opíše do sebe stav uložení dat (user + time),
        /// tak abychom mohli před přechodem do stavu NowEditing porovnat aktuální stav z databáze.
        /// </summary>
        internal void SetCurrentDataTimestamp()
        {
            using (PlanCVersionHdrCls currentDbData = this.LoadCurrentData(false))
            {   // načtu si data z DB, a z nich si uložím otisky:
                this.TimeStampLastPlanUser = currentDbData.LastPlanUser;
                this.TimeStampLastPlanTime = currentDbData.LastPlanTime;
            }
        }
        /// <summary>
        /// Vrací true, pokud data (timestamp) v databázi načtená v daném objektu se shodují s timestampem uloženým v this objektu.
        /// </summary>
        /// <param name="currentDbData"></param>
        /// <returns></returns>
        internal bool IsCorrectTimeStamp(PlanCVersionHdrCls currentDbData)
        {
            return (this.TimeStampLastPlanUser.IsEqual(currentDbData.LastPlanUser) && this.TimeStampLastPlanTime.IsEqual(currentDbData.LastPlanTime));
        }
        /// <summary>
        /// Hodnota "Stav procesu" načtená z databáze v okamžiku, kdy přecházím do stavu NowEditing
        /// </summary>
        protected PlanningProcessState PreEdDbProcessState { get; private set; }
        /// <summary>
        /// Hodnota "WorkUser" načtená z databáze v okamžiku, kdy přecházím do stavu NowEditing
        /// </summary>
        protected SqlInt32 PreEdDbWorkingUser { get; private set; }
        /// <summary>
        /// Hodnota "WorkTime" načtená z databáze v okamžiku, kdy přecházím do stavu NowEditing
        /// </summary>
        protected SqlDateTime PreEdDbWorkingTime { get; private set; }
        /// <summary>
        /// TimeStamp: stav údaje LastPlanUser v době posledního načtení dat z databáze, nebo po posledním našem uložení do databáze.
        /// Umožňuje hlídat, zda v naší paměti je stav plánu vycházející z dat, která jsou v databázi.
        /// Pokud mezitím někdo jiný uloží data do databáze, přepíše i databázové hodnoty LastPlanUser a LastPlanTime, a my poznáme že naše data jsou passe.
        /// </summary>
        protected SqlInt32 TimeStampLastPlanUser { get; private set; }
        /// <summary>
        /// TimeStamp: stav údaje LastPlanTime v době posledního načtení dat z databáze, nebo po posledním našem uložení do databáze.
        /// Umožňuje hlídat, zda v naší paměti je stav plánu vycházející z dat, která jsou v databázi.
        /// Pokud mezitím někdo jiný uloží data do databáze, přepíše i databázové hodnoty LastPlanUser a LastPlanTime, a my poznáme že naše data jsou passe.
        /// </summary>
        protected SqlDateTime TimeStampLastPlanTime { get; private set; }
        #endregion
        #region KONFIGURACE MODULU VÝROBA
        /// <summary>
		/// Konfigurace modulu Výroba
		/// </summary>
		public MfrModulConfigCls MfrModulConfig
		{
			get 
			{
				if (this._MfrModulConfig == null)
				{
					int rec = Db_Layer.GetItem<int>("SELECT top 1 cislo_nonsubjektu FROM lcs.mfr_modul_config");
					this._MfrModulConfig = new MfrModulConfigCls(rec);
				}
				return this._MfrModulConfig;
			}
		}
		private MfrModulConfigCls _MfrModulConfig = null;
		/// <summary>
		/// Verze určená pro výrobu = ve statickém vztahu z konfigurace modulu
		/// </summary>
		public int ModulConfigVersionForProduct
		{
			get
			{
				SqlInt32 version = this.MfrModulConfig.VersionForProduct;
				return (version.IsNull ? 0 : version.Value);
			}
		}
		#endregion
		#region PROPERTY napojené na záznam třídy 1362: Kapacitní plán verze
		/// <summary> { get; }
		/// Jak pracovat s minimální zásobou
		/// </summary>
		public MinimumStockQtyMode MinStockQtyMode { get { return this.PlanCVersionData.MrpMinStockQtyMode; } }
		/// <summary> { get; }
		/// Defaultní agregační perioda = doba (počet dnů), po kterou je možno slučovat požadavky do jednoho zajištění.
		/// Zde je uvedeno číslo Int32 = počet dnů.
		/// Je vhodnější pouřít property DefaultAggregationTime, kde je uložen přesný agregační čas.
		/// Obě property jsou svázány.
		/// </summary>
		public int DefaultAggregationPeriod { get { return this.PlanCVersionData.MrpAggregationPeriod; } }
		/// <summary> { get; }
		/// Defaultní agregační perioda = exaktně určený časový úsek, po kterou je možno slučovat požadavky do jednoho zajištění.
		/// </summary>
		public TimeSpan DefaultAggregationTime { get { return this.PlanCVersionData.MrpAggregationPeriodTime; } }
		/// <summary> { get; }
		/// Příznak, zda se v procesu má akceptovat dělitel množství ze stavu skladu, při tvorbě návrhu na příjem.
		/// </summary>
		public bool AcceptQtyDivider { get { return this.PlanCVersionData.MrpAcceptQtyDivider; } }
		/// <summary> { get; }
		/// Režim vyhledávání T modifikace pro plánování
		/// </summary>
		internal FindAOperationModDateMode AOperationModDateMode { get { return this.PlanCVersionData.MrpAOperationModFindMode; } }
		/// <summary> { get; }
		/// Režim termínování komponent (čas výdeje komponent, čas příjmu vedlejších produktů)
		/// </summary>
		internal MrpStructureTimingType MrpStructTiming { get { return this.PlanCVersionData.MrpStructTiming; } }
		/// <summary> { get; }
		/// Posunutí datumu návrhu na zajištění minimální zásoby oproti datu procesu (+- počet dní)
		/// </summary>
		public double MrpMinQtyDays { get { return this.PlanCVersionData.MrpMinQtyDays;} }
		/// <summary> { get; }
		/// Akceptovat disponibilní zásobu na stavu skladu (mrp_fund_reflect)
		/// </summary>
		public bool MrpAcceptStockFund { get { return this.PlanCVersionData.MrpAcceptStockFund; } }
		/// <summary>
		/// Množství "Minimum od dodavatele" (ze stavu skladu) uvažovat i pro návrhy pro zajištění Minimální zásoby
		/// </summary>
		public bool MrpMinDodToMinQty { get { return (String.Equals(this.PlanCVersionData.MrpMindodToMinlevel, "A")); } }
		/// <summary>
		/// Pracovat s kategoriemi?
		/// </summary>
		public bool MrpWorkWithCategories { get { return (String.Equals(this.PlanCVersionData.MrpWorkWithCategories, "A")); } }
		/// <summary> { get; }
		/// Je tato verze plánu verzí výrobní?
		/// </summary>
		public bool IsVersionForProduct { get { return (this.PlanCVersion == this.ModulConfigVersionForProduct); } }
		/// <summary> { get; }
		/// Při ukládání výsledků ukládat i stavy skladů plánovacích jednotek, a stavy do záznamů plánovacích jednotek.
		/// true = ukládat / false = ne.
		/// Ukládat by měla jen hlavní verze plánu, protože stavy skladů a PJ jsou jen 1x, pro všechny verze plánu.
		/// </summary>
		public bool SaveStockLevelData { get { return this.IsVersionForProduct; } }
		/// <summary> { get; }
		/// Při ukládání výsledků ukládat i nová data výrobních příkazů a operací (fixace a termíny).
		/// true = ukládat / false = ne.
		/// Ukládat by měla jen hlavní verze plánu, protože tato data jsou k dispozici jen 1x, pro všechny verze plánu.
		/// </summary>
		public bool SaveProductDataChanges { get { return this.IsVersionForProduct; } }
        /// <summary>{ get; }
        /// Atribut: Používat dělení na výrobní dávky (základní = mezi sousedními operacemi)
        /// </summary>
        public bool UseMoveBatchQty { get { return (String.Equals(this.PlanCVersionData.UseMoveBatchQty, "A")); } }
        /// <summary>{ get; }
        /// Atribut: Výrobní dávky i na další dílec (rozšířené = i přes skladové převody)
        /// </summary>
        public bool UseMoveBatchTransfer { get { return (String.Equals(this.PlanCVersionData.UseMoveBatchTransfer, "A")); } }
		/// <summary>
		/// Režim alokace kapacit pro běžné tasky (nefixované)
		/// </summary>
		public LimitedCType RegisterTaskLimitCType { get { return this.PlanCVersionData.RegisterTaskLimitCType; } }
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Pracoviště
		/// </summary>
		internal SourceFixUnitMode SourceFixModeWorkplace { get { return this.PlanCVersionData.SourceFixModeWorkplace; } }
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Profese
		/// </summary>
		internal SourceFixUnitMode SourceFixModeProfession { get { return this.PlanCVersionData.SourceFixModeProfession; } }
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Zdroj operace
		/// </summary>
		internal SourceFixUnitMode SourceFixModeResource { get { return this.PlanCVersionData.SourceFixModeResource; } }
		/// <summary>
		/// Registrace kapacit: zajistit kapacitu pro Pracoviště na operaci
		/// </summary>
		internal bool CapacityRegisterForWorkplace { get { return this._CapacityRegisterFor(this.PlanCVersionData.SourceFixModeWorkplace); } }
		/// <summary>
		/// Registrace kapacit: zajistit kapacitu pro Profesi ze Standardní operace
		/// </summary>
		internal bool CapacityRegisterForProfession { get { return this._CapacityRegisterFor(this.PlanCVersionData.SourceFixModeProfession); } }
		/// <summary>
		/// Registrace kapacit: zajistit kapacitu pro Zdroje operací
		/// </summary>
		internal bool CapacityRegisterForResources { get { return this._CapacityRegisterFor(this.PlanCVersionData.SourceFixModeResource); } }
		/// <summary>
		/// Registrace kapacit: režim alokace času MoveTime
		/// </summary>
		internal AllocMode CapacityMoveTimeAllocMode { get { return this.PlanCVersionData.CapacityMoveTimeAllocMode; } }
		/// <summary>
		/// Registrace kapacit: kalendář, podle kterého se určuje čas MoveTime v případě režimu CapacityMoveTimeAllocMode = "Podle konkrétního kalendáře"
		/// </summary>
		internal int CapacityMoveTimeCalendar { get { return (this.PlanCVersionData.CplMoveTimeCalend.IsNull ? 0 : this.PlanCVersionData.CplMoveTimeCalend.Value); } }
		/// <summary>
		/// Registrace kapacit: režim alokace času v případě, kdy neexistují kapacitní plánovací jednotky
		/// </summary>
		internal AllocMode CapacityNoneUnitAllocMode { get { return this.PlanCVersionData.CapacityNoneUnitAllocMode; } }
        /// <summary>
		/// Registrace kapacit: kalendář, podle kterého se určuje čas operace bez kapacitních jednotek v případě režimu CapacityNoneUnitAllocMode = "Podle konkrétního kalendáře"
		/// </summary>
		internal int CapacityNoneUnitCalendar { get { return (this.PlanCVersionData.CplNoneTimeCalend.IsNull ? 0 : this.PlanCVersionData.CplNoneTimeCalend.Value); } }
        /// <summary>
        /// Způsob termínování operací (Podle lhůt na stavu skladu / podle termínů STPV / podle systému kapacit).
        /// </summary>
        internal MrpOperationTimingType OperationTiming { get { return this.PlanCVersionData.OperationTiming; } }
        /// <summary>
        /// Varianta plánovacího procesu určená ve verzi kapacitního plánu.
        /// Volbou kapacitního plánu se najde a předvolí i variátor, jehož Order odpovídá této variantě.
        /// </summary>
        internal Int32? PlanningVariation { get { return this.PlanCVersionData.PlanningVariation.ToInt32N(); } }
        #endregion
		#region PROPERTY z vlastních zdrojů
		/// <summary> { get; set; }
		/// Příznak, zda je možno při tvorbě návrhů na zajištění provádět kumulaci z vhodných požadavků (true = současné chování, default)
		/// anebo je třeba generovat návrhy jednotlivě bez kumulací (false)
		/// </summary>
		public bool EnableProposalCumulating
		{
			get { return this._EnableProposalCumulating; }
			internal set { this._EnableProposalCumulating = value; }
		}
		private bool _EnableProposalCumulating = true;
		/// <summary> { get; set; }
		/// Příznak, zda se v procesu má akceptovat minimum od dodavatele ze stavu skladu, při tvorbě návrhu na příjem.
		/// </summary>
		public bool AcceptMinimumDodavatel
		{
			get { return _AcceptMinimumDodavatel; } 
			internal set { _AcceptMinimumDodavatel = value; }
		}
		private bool _AcceptMinimumDodavatel = true;
		/// <summary> { get; set; }
		/// Příznak, zda se v procesu má akceptovat optimální vyráběná dávka, při tvorbě návrhu na příjem.
		/// </summary>
		public bool AcceptOptimumBatch 
		{
			get { return _AcceptOptimumBatch;} 
			internal set { _AcceptOptimumBatch = value; }
		}
		private bool _AcceptOptimumBatch = true;
		/// <summary> { get; set; }
		/// Režim načítání disponibilní kapacity při načítání dat (načíst nebo přepočítat)
		/// </summary>
		public PlanningLoadCapacityMode LoadCapacityDisponibleMode
		{
			get { return _LoadCapacityDisponibleMode; } 
			internal set { _LoadCapacityDisponibleMode = value; } 
		}
		private PlanningLoadCapacityMode _LoadCapacityDisponibleMode = PlanningLoadCapacityMode.RecalcCapacityByWork;
		/// <summary>
		/// Registrace kapacit: požadavek, aby se ukládaly stavy kapacit i za kapacitní jednotky, které nemají naplánovanou žádnou práci (vůbec !)
		/// </summary>
		internal bool CapacitySaveCClEmptyUnits
		{
			get { return _CapacitySaveCClEmptyUnits; }
			set { _CapacitySaveCClEmptyUnits = value; } 
		}
		private bool _CapacitySaveCClEmptyUnits = false;
		#endregion
		#region KONVERZNÍ METODY
		/// <summary>
		/// Vrátí režim fixace kapacit pro kapacitní zdroj dané kategorie.
		/// Vstupme je kategorie kapacitního zdroje, a tato metoda najde a vrátí jednu ze svých property, která odpovídá tomuto zdroji.
		/// </summary>
		/// <param name="capacitySourceCategory"></param>
		/// <returns></returns>
		internal SourceFixUnitMode GetSourceFixUnitModeForCategory(CapacitySourceCategory capacitySourceCategory)
		{
			switch (capacitySourceCategory)
			{
				case CapacitySourceCategory.Workplace:
					return this.SourceFixModeWorkplace;
				case CapacitySourceCategory.Profession:
					return this.SourceFixModeProfession;
				case CapacitySourceCategory.Resource:
					return this.SourceFixModeResource;
			}
			return SourceFixUnitMode.AnyOfAvailable;
		}
		/// <summary>
		/// Určí, zda se má registrovat kapacita pro určitý zdroj, podle zadání jeho fixace na KPJ 
		/// (režim fixace může obsahovat i hodnotu NoPlanning = neplánovat).
		/// </summary>
		/// <param name="fixMode">režim fixace zdroje</param>
		/// <returns></returns>
		private bool _CapacityRegisterFor(SourceFixUnitMode fixMode)
		{
			return (fixMode == SourceFixUnitMode.AnyOfAvailable || fixMode == SourceFixUnitMode.Conservative || fixMode == SourceFixUnitMode.ConstantTemporal || fixMode == SourceFixUnitMode.ConstantPermanent);
		}
		#endregion
		#region BARVY
		/// <summary>
		/// Barvy používané pro grafickou reprezentaci řádků, elementů a vztahů
		/// </summary>
		public PlanningColorCls Colors
		{
			get
			{
				if (this._Colors == null)
					this._Colors = PlanningColorCls.CreateDefault();
				return this._Colors;
			}
		}
		private PlanningColorCls _Colors;
		#endregion
    }
	#endregion
	#region CLASS PlanningColorCls : Barvy používané pro grafickou reprezentaci řádků, elementů a vztahů
	public class PlanningColorCls
	{
		#region KONSTUKCE
		private PlanningColorCls()
		{ }
		public static PlanningColorCls CreateDefault()
		{
			PlanningColorCls colors = new PlanningColorCls();
			colors.ElementGanttAxisSMainEnqReal = Color.FromArgb(0, 66, 218);
			colors.ElementGanttAxisSMainEnqPlan = Color.FromArgb(0, 128, 240);
			colors.ElementGanttAxisSMainProdMin = Color.FromArgb(218, 66, 0);
			colors.ElementGanttAxisSMainProdReal = Color.FromArgb(0, 218, 66);
			colors.ElementGanttAxisSMainProdPlan = Color.FromArgb(0, 218, 240);
			colors.ElementGanttAxisSMainOther = Color.FromArgb(192, 216, 255);

			colors.ElementTLinkAxisSMainEnqReal = colors.ElementGanttAxisSMainEnqReal;
			colors.ElementTLinkAxisSMainEnqPlan = colors.ElementGanttAxisSMainEnqPlan;
			colors.ElementTLinkAxisSMainProdMin = colors.ElementGanttAxisSMainProdMin;
			colors.ElementTLinkAxisSMainProdReal = colors.ElementGanttAxisSMainProdReal;
			colors.ElementTLinkAxisSMainProdPlan = colors.ElementGanttAxisSMainProdPlan;
			colors.ElementTLinkAxisSMainOther = colors.ElementGanttAxisSMainOther;


			return colors;
		}
		#endregion
		#region NAČÍTÁNÍ Z XML SOUBORU

		#endregion
		#region PROPERTY JEDNOTLIVÝCH BAREV

		/// <summary>
		/// Barva elementu grafu Využití kapacit, barva podkladu stavu kapacit, pokud není určeno přímo pracovní směnou
		/// </summary>
		public Color ElementCapacityUtilLevel { get; private set; }
		/// <summary>
		/// Barva elementu grafu Využití kapacit, linka běžící po vrcholku položek
		/// </summary>
		public Color ElementCapacityUtilLine { get; private set; }
		/// <summary>
		/// Barva elementu grafu Využití kapacit, barva při nulovém využití kapacit
		/// </summary>
		public Color ElementCapacityUtilFree { get; private set; }
		/// <summary>
		/// Barva elementu grafu Využití kapacit, barva při plném využití kapacit
		/// </summary>
		public Color ElementCapacityUtilFull { get; private set; }
		/// <summary>
		/// Barva elementu grafu Využití kapacit, cílová barva při překročení kapacit (platí pro využití kapacit na 300%)
		/// </summary>
		public Color ElementCapacityUtilHigh { get; private set; }

		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, poptávka reálná
		/// </summary>
		public Color ElementGanttAxisSMainEnqReal { get; private set; }
		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, poptávka reálná
		/// </summary>
		public Color ElementGanttAxisSMainEnqPlan { get; private set; }
		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, výrobní příkaz pro minimální zásobu
		/// </summary>
		public Color ElementGanttAxisSMainProdMin { get; private set; }
		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, výrobní příkaz reálný, nadbytečný
		/// </summary>
		public Color ElementGanttAxisSMainProdReal { get; private set; }
		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, výrobní příkaz plánovaný, nadbytečný
		/// </summary>
		public Color ElementGanttAxisSMainProdPlan { get; private set; }
		/// <summary>
		/// Barva elementu grafu Gantt, osy S, hlavní řádek, ostatní řádky
		/// </summary>
		public Color ElementGanttAxisSMainOther { get; private set; }

		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, poptávka reálná
		/// </summary>
		public Color ElementTLinkAxisSMainEnqReal { get; private set; }
		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, poptávka reálná
		/// </summary>
		public Color ElementTLinkAxisSMainEnqPlan { get; private set; }
		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, výrobní příkaz pro minimální zásobu
		/// </summary>
		public Color ElementTLinkAxisSMainProdMin { get; private set; }
		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, výrobní příkaz reálný, nadbytečný
		/// </summary>
		public Color ElementTLinkAxisSMainProdReal { get; private set; }
		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, výrobní příkaz plánovaný, nadbytečný
		/// </summary>
		public Color ElementTLinkAxisSMainProdPlan { get; private set; }
		/// <summary>
		/// Barva elementu grafu TaskLink, osy S, hlavní řádek, ostatní řádky
		/// </summary>
		public Color ElementTLinkAxisSMainOther { get; private set; }
		
		
		#endregion
	}
	#endregion
	#region INTERFACE IRecordItemKey, ENUMS: parametry běhu, auditlog informací
	/// <summary>
	/// Interface, který deklaruje přítomnost property, které specifikují Green záznam (třída, pořadač, číslo záznamu, číslo položky)
	/// </summary>
	internal interface IRecordItemKey
	{
		/// <summary>Číslo třídy, povinně</summary>
		int ClassNumber { get; }
		/// <summary>Číslo pořadače, nebo 0 když není známo (nebude se předávat do auditlogu)</summary>
		int FolderNumber { get; }
		/// <summary>Číslo subjektu/nonsubjektu</summary>
		int CisloSubjektu { get; }
		/// <summary>Číslo objektu, nebo 0 když nejsou položky</summary>
		int CisloObjektu { get; }
	}
	#endregion
}
#region Extenze datových tříd (namespace Planning.DataFace)
namespace Noris.Schedule.Planning.DataFace
{
	#region Extenze verze kapacitního plánu: obsahuje typově konvertované parametry
	public partial class PlanCVersionHdrCls : RecordCls, Noris.Schedule.Support.Sql.IDbRecord
    {
        #region Create default
        /// <summary>
        /// Obsahuje (vždy vrátí nový) prázdný objekt, použitelný jako základ pro defaultní objekty.
        /// </summary>
        public static PlanCVersionHdrCls Empty
        {
            get
            {
                PlanCVersionHdrCls data = new PlanCVersionHdrCls(false);
                data._cpl_puc_limited = EnumConvert.TypeToDb(LimitedCType.ByPUCsetting);
                data._cpl_plan_workplace = EnumConvert.TypeToDb(SourceFixUnitMode.Conservative);
                data._cpl_plan_profession = EnumConvert.TypeToDb(SourceFixUnitMode.AnyOfAvailable);
                data._cpl_plan_resource = EnumConvert.TypeToDb(SourceFixUnitMode.Conservative);
                data._mrp_structure_timing = EnumConvert.TypeToDb(MrpStructureTimingType.ByTask);
                data._mrp_minimum_stock_reflect = EnumConvert.TypeToDb(MinimumStockQtyMode.OnFirstProposal);
                data._sel_mrp_mod = EnumConvert.TypeToDb(FindAOperationModDateMode.DateStart);
                data.MrpAggregationPeriod = 0;
                return data;
            }
        }
        /// <summary>
        /// Obsahuje (vždy vrátí nový) prázdný objekt, použitelný jako default pro implicitní běh.
        /// </summary>
        public static PlanCVersionHdrCls Default
        {
            get
            {
                PlanCVersionHdrCls data = Empty;
                data._mrp_minimum_stock_reflect = EnumConvert.TypeToDb(MinimumStockQtyMode.OnFirstProposal);
                data._sel_mrp_mod = EnumConvert.TypeToDb(FindAOperationModDateMode.DateStart);
                data.MrpAggregationPeriod = 0;
                return data;
            }
        }
        /// <summary>
        /// Obsahuje (vždy vrátí nový) prázdný objekt, použitelný jako default pro běh.
        /// </summary>
        public static PlanCVersionHdrCls DefaultForRun
        {
            get
            {
                PlanCVersionHdrCls data = Empty;
                data._mrp_minimum_stock_reflect = EnumConvert.TypeToDb(MinimumStockQtyMode.OnPlanBegin);
                data._sel_mrp_mod = EnumConvert.TypeToDb(FindAOperationModDateMode.DateStart);
                data.MrpAggregationPeriod = 7;
                return data;
            }
        }
        /// <summary>
        /// Obsahuje (vždy vrátí nový) prázdný objekt, použitelný jako default pro běh.
        /// </summary>
        public static PlanCVersionHdrCls DefaultForTest
        {
            get
            {
                PlanCVersionHdrCls data = Empty;
                data._mrp_minimum_stock_reflect = EnumConvert.TypeToDb(MinimumStockQtyMode.OnPlanBegin);
                data._sel_mrp_mod = EnumConvert.TypeToDb(FindAOperationModDateMode.DateStart);
                data.MrpAggregationPeriod = 7;
                return data;
            }
        }
        /// <summary>
        /// Nastaví dané hodnoty do parametrů procesu, pro testovací účely
        /// </summary>
        /// <param name="timing"></param>
        /// <param name="period"></param>
        /// <param name="minimumStockQtyMode"></param>
        internal void SetTestParams(MrpStructureTimingType timing, int period, MinimumStockQtyMode minimumStockQtyMode)
        {
            this._mrp_structure_timing =  EnumConvert.TypeToDb(timing);
            this.MrpAggregationPeriod = period;
            this._mrp_minimum_stock_reflect = EnumConvert.TypeToDb(minimumStockQtyMode);
        }
        /// <summary>
        /// Override ToString: verze se používá obecně i na vizuální vrstvě, netypově jako object, a její ToString() slouží pro vizuální reprezentaci na Formu
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.ReferNazev;
        }
        #endregion
        #region Dispečer stavu plánovacího procesu pro tuto verzi, podpora (CurrentState, GetDbInfo)
        /// <summary>
        /// Aktuální stav plánovacího procesu.
        /// Tato property v set metodě nemá žádnou logiku typu Dispečer !!!
        /// Setování hodnoty doplní hodnoty do working_user a do working_time, a okamžitě data ukládá do databáze.
        /// V případě změny stavu na stav AfterSave, nebo ze stavu SavingData na stav Save uloží i hodnoty do last_plan_user a last_plan_time, a zapíše je do databáze.
        /// Logiku typu Dispečer obsahuje metoda this.SetProcessState()
        /// </summary>
        public PlanningProcessState CurrentProcessState
        {
            get { return EnumConvert.DbToType(this._planning_state, PlanningProcessState.None); }
            internal set
            {
                bool saveWork = false;
                bool saveLast = false;

                PlanningProcessState prevState = this.CurrentProcessState;
                SqlInt32 currUser = new SqlInt32(Steward.CurrentUser.Osoba);
                SqlDateTime currTime = new SqlDateTime(DateTime.Now);

                if (value == PlanningProcessState.PreEdit)
                {
                    this._planning_state = EnumConvert.TypeToDb(value);
                    this._working_user = currUser;
                    this._working_time = currTime;
                }
                else
                {
                    bool isChangeState = (value != prevState);
                    if (isChangeState || currUser != this._working_user)
                    {
                        this._planning_state = EnumConvert.TypeToDb(value);
                        this._working_user = currUser;
                        this._working_time = currTime;
                        saveWork = true;
                    }

                    if (isChangeState && (value == PlanningProcessState.AfterSave || (prevState == PlanningProcessState.SavingData && value == PlanningProcessState.Save)))
                    {
                        this._last_plan_user = currUser;
                        this._last_plan_time = currTime;
                        saveLast = true;
                    }
                    this.SaveWorkingStateData(saveWork, saveLast);
                }
            }
        }
        /// <summary>
        /// Do Working údajů (planning_state, working_user, working_time) vepíše data z parametrů a uloží do DB.
        /// Proč tak explicitně? Aby bylo možno do Work údajů vrátit to, co tam bylo před naším startem (tj. i jiného uživatele a historický čas).
        /// </summary>
        /// <param name="state">Požadovaný stav</param>
        /// <param name="workingUser">Uživatel do working_user</param>
        /// <param name="workingTime">Čas do working_time</param>
        internal void DirectSetWorkingState(PlanningProcessState state, SqlInt32 workingUser, SqlDateTime workingTime)
        {
            this._planning_state = EnumConvert.TypeToDb(state);
            this._working_user = workingUser;
            this._working_time = workingTime;
            this.SaveWorkingStateData(true, false);
        }
        /// <summary>
        /// Uloží data planning_state, working_user, working_time a last_plan_user, last_plan_time do databáze.
        /// </summary>
        /// <param name="saveLast">Uložit planning_state, working_user, working_time</param>
        /// <param name="saveWork">Uložit last_plan_user, last_plan_time</param>
        private void SaveWorkingStateData(bool saveWork, bool saveLast)
        {
            if (!saveWork && !saveLast) return;

            string sql = "UPDATE lcs.plan_c_version_hdr SET " +
                (saveWork ? "planning_state = " + SqlFormat.ToSql(this._planning_state) + "," +
                            "working_user = " + SqlFormat.ToSql(this._working_user) + "," +
                            "working_time = " + SqlFormat.ToSql(this._working_time) : "")
                            +
                (saveWork && saveLast ? "," : "")
                            +
                (saveLast ? "last_plan_user = " + SqlFormat.ToSql(this._last_plan_user) + "," +
                            "last_plan_time = " + SqlFormat.ToSql(this._last_plan_time) : "")
                            +
                " WHERE cislo_subjektu = " + SqlFormat.ToSql(this.RecordNumber);
            Noris.Schedule.Support.Sql.Db_Layer.ExecuteNonQuery(sql);
        }
        /// <summary>
        /// Vrátí textovou informaci o aktuálním DB stavu,
        /// typicky: "Uživatel XYZ (provádí akci AAA) (od času TTT)" (bez tečky na konci)
        /// </summary>
        /// <param name="currentDbData"></param>
        /// <returns></returns>
        internal static string GetDbWorkingInfo(PlanCVersionHdrCls currentDbData)
        {
            string userText = "";
            if (!currentDbData.WorkingUser.IsNull)
            {
                NorisUserCls userData = Db_Layer.GetRecord<NorisUserCls>(NorisUserCls.SqlSelect + " WHERE " + NorisUserCls.SqlColumnOsoba + " = " + currentDbData.WorkingUser.Value.ToString());
                if (userData != null)
                    userText = userData.OsobaNazev;
            }
            string timeText = "";
            if (!currentDbData.WorkingTime.IsNull)
            {
                timeText = currentDbData.WorkingTime.Value.ToString(System.Globalization.CultureInfo.CurrentUICulture.DateTimeFormat);
            }
            switch (currentDbData.CurrentProcessState)
            {   // Stav procesu v databázi:
                case PlanningProcessState.None:
                case PlanningProcessState.NotExist:
                    return "Data plánu neexistují";
                case PlanningProcessState.PreEdit:
                    return "Uživatel " + userText + " se chystá editovat data, od času " + timeText;
                case PlanningProcessState.NowEditing:
                    return "Uživatel " + userText + " nyní edituje data, od času " + timeText;
                case PlanningProcessState.SavingData:
                    return "Uživatel " + userText + " nyní ukládá data, od času " + timeText;
                case PlanningProcessState.AfterSave:
                    return "Uživatel " + userText + " uložil data a chystá je dále editovat, od času " + timeText;
                case PlanningProcessState.Save:
                    return "Uživatel " + userText + " uložil data, v čase " + timeText;
            }
            return "Data plánu nejsou korektní";
        }
        /// <summary>
        /// Vrátí textovou informaci o tom, kdo a kdy naposledy uložil data plánu,
        /// typicky: "Uživatel XYZ uložil data, v čase TTT" (bez tečky na konci)
        /// </summary>
        /// <param name="currentDbData"></param>
        /// <returns></returns>
        internal static string GetDbLastPlanInfo(PlanCVersionHdrCls currentDbData)
        {
            string userText = "";
            if (!currentDbData.LastPlanUser.IsNull)
            {
                NorisUserCls userData = Db_Layer.GetRecord<NorisUserCls>(NorisUserCls.SqlSelect + " WHERE " + NorisUserCls.SqlColumnOsoba + " = " + currentDbData.LastPlanUser.Value.ToString());
                if (userData != null)
                    userText = userData.OsobaNazev;
            }
            string timeText = "";
            if (!currentDbData.LastPlanTime.IsNull)
            {
                timeText = currentDbData.LastPlanTime.Value.ToString(System.Globalization.CultureInfo.CurrentUICulture.DateTimeFormat);
            }
            return "Uživatel " + userText + " uložil data, v čase " + timeText;
        }
        #endregion
        #region MRP
        ///<summary>Inicializovat osu S: z Poptávek, Objednávek a Potvrz. Obj. (true / false)</summary>
		public bool IsMrpInitEnquireReflect
		{
			get { return EnumConvert.DbToType(this.MrpInitEnquireReflect, false); }
		}
		///<summary>Inicializovat osu S: z neodvedených zůstatků sestav VP (true / false)</summary>
		public bool IsMrpInitPoReflect
		{
			get { return EnumConvert.DbToType(this.MrpInitPoReflect, false); }
		}
		///<summary>Inicializovat osu S: z předpokl. výdejů do kusovníků VP (true / false)</summary>
		public bool IsMrpInitPoStructureReflect
		{
			get { return EnumConvert.DbToType(this.MrpInitPoStructureReflect, false); }
		}
        ///<summary>Inicializovat osu S: z nerealizovaných poptávek plánu (true / false)</summary>
        public bool IsMrpInitPlanEnquireReflect
        {
            get { return EnumConvert.DbToType(this.MrpInitPlanEnquireReflect, false); }
        }
		/// <summary> { get; }
		/// Uvažovat min. množství skladu: Neuvažovat; Zajistit min. množství k datu prvního návrhu; Zajistit min. množství po celou dobu plánu
		/// </summary>
		public MinimumStockQtyMode MrpMinStockQtyMode
		{
			get { return EnumConvert.DbToType(this.MrpMinimumStockReflect, MinimumStockQtyMode.NoMinQty); }
		}
		/// <summary> { get; }
		/// Defaultní agregační perioda = doba (počet dnů), po kterou je možno slučovat požadavky do jednoho zajištění.
		/// Zde je uvedeno číslo Int32 = počet dnů.
		/// Je vhodnější pouřít property DefaultAggregationTime, kde je uložen přesný agregační čas.
		/// Obě property jsou svázány.
		/// </summary>
		public int MrpAggregationPeriod
		{
			get
			{
				if (this.MrpAggrPeriod.IsNull) return 0;
				int period = this.MrpAggrPeriod.Value;
				if (period != 0 && this._MrpAggrPeriodTime.Ticks == 0)
					_MrpAggrPeriodTime = new TimeSpan(period, 0, 0, 0); 
				return (int)(Math.Round(_MrpAggrPeriodTime.TotalDays, 0)); 
			} 
			internal set 
			{
                _mrp_aggr_period = new SqlInt32(value);
				_MrpAggrPeriodTime = new TimeSpan(value, 0, 0, 0); 
			}
		}
		/// <summary> { get; }
		/// Defaultní agregační perioda = exaktně určený časový úsek, po kterou je možno slučovat požadavky do jednoho zajištění.
		/// </summary>
		public TimeSpan MrpAggregationPeriodTime
		{
			get
			{
				if (this.MrpAggrPeriod.IsNull) return TimeSpan.Zero;
				int period = this.MrpAggrPeriod.Value;
				if (period != 0 && this._MrpAggrPeriodTime.Ticks == 0)
					_MrpAggrPeriodTime = new TimeSpan(period, 0, 0, 0);
				return _MrpAggrPeriodTime;
			} 
            /*
			internal set 
			{
				if (value.Ticks == 0)
					this.MrpAggrPeriod = SqlInt32.Null;
				else
					this.MrpAggrPeriod = new SqlInt32((int)(Math.Round(value.TotalDays, 0)));
				_MrpAggrPeriodTime = value;
			}
             */
		}
		private TimeSpan _MrpAggrPeriodTime;
		/// <summary> { get; }
		/// Příznak, zda se v procesu má akceptovat dělitel množství ze stavu skladu, při tvorbě návrhu na příjem.
		/// </summary>
		public bool MrpAcceptQtyDivider
		{
			get { return (this.MrpQtyDividerReflect == "A"); }
		}
		/// <summary> { get; }
		/// Režim vyhledávání T modifikace pro plánování
		/// </summary>
		public FindAOperationModDateMode MrpAOperationModFindMode
		{
			get { return EnumConvert.DbToType(this.SelMrpMod, FindAOperationModDateMode.DateStart); }
		}
		/// <summary> { get; }
		/// Režim termínování komponent (čas výdeje komponent, čas příjmu vedlejších produktů)
		/// </summary>
		public MrpStructureTimingType MrpStructTiming
		{
			get { return EnumConvert.DbToType(this.MrpStructureTiming, MrpStructureTimingType.ByTask); }
		}
		/// <summary> { get; }
		/// Posunutí datumu návrhu na zajištění minimální zásoby oproti datu procesu (+- počet dní)
		/// </summary>
		public double MrpMinQtyDays
		{
			get
			{
				if (this.MrpMinqtyDays.IsNull) return 0D;
				int value = this.MrpMinqtyDays.Value;
				if (value != 0 && this._MrpMinQtyDays == 0D)
					this._MrpMinQtyDays = (double)value;
				return this._MrpMinQtyDays;
			} 
            /*
			set
			{
				if (value == 0D)
					this.MrpMinqtyDays = SqlInt32.Null;
				else
					this.MrpMinqtyDays = new SqlInt32((int)Math.Round(value, 0));
				this._MrpMinQtyDays = value;
			} 
            */
		}
		private double _MrpMinQtyDays = 0D;
		/// <summary> { get; }
		/// Akceptovat disponibilní zásobu na stavu skladu (mrp_fund_reflect)
		/// </summary>
		public bool MrpAcceptStockFund
		{
			get { return (this.MrpFundReflect == "A"); }
		}
		/// <summary>
		/// Režim načítání položek obchodního plánu
		/// </summary>
		internal MrpBussinesPlanMode MrpBussinesPlanLoadMode
		{
			get { return EnumConvert.DbToType(this.MrpPlanNolink, this.MrpInitPlanEnquireReflect, MrpBussinesPlanMode.NotRead); }
			// set { this.MrpInitPlanEnquireReflect = EnumConvert.TypeToDb(value, "N"); this.MrpPlanNolink = EnumConvert.TypeToDb(value, MrpBussinesPlanMode.NotRead); }
		}
        /// <summary>
        /// Režim zaokrouhlení data položek obchodního plánu
        /// </summary>
        internal MrpBussinesPlanDateRoundMode MrpBussinesPlanDateRoundMode
        {
            get { return EnumConvert.DbToType(this.MrpPlanDateRoundType, MrpBussinesPlanDateRoundMode.Maximum); }
        }
        /// <summary>
        /// Způsob termínování operací (Podle lhůt na stavu skladu / podle termínů STPV / podle systému kapacit).
        /// </summary>
        internal MrpOperationTimingType OperationTiming
        {
            get { return EnumConvert.DbToType(this.MrpTimingType, MrpOperationTimingType.NotDefined); }
        }
        #endregion
        #region KPL
        /// <summary>
        /// Registrace kapacit: režim alokace času MoveTime
        /// </summary>
        internal AllocMode CapacityMoveTimeAllocMode
        {
            get { return EnumConvert.DbToType(this.CplMoveTimeType, AllocMode.CalculatePlainTime); }
        }
		/// <summary>
		/// Registrace kapacit: režim alokace času v případě, kdy neexistují kapacitní plánovací jednotky
		/// </summary>
		internal AllocMode CapacityNoneUnitAllocMode
        {
            get { return EnumConvert.DbToType(this.CplNoneTimeType, AllocMode.CalculatePlainTime); }
        }
		/// <summary>
		/// Režim alokace kapacit pro běžné tasky (nefixované)
		/// </summary>
		public LimitedCType RegisterTaskLimitCType
		{
			get { return EnumConvert.DbToType(this.CplPucLimited, LimitedCType.ByPUCsetting); }
		}
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Pracoviště
		/// </summary>
		internal SourceFixUnitMode SourceFixModeWorkplace
		{
			get { return EnumConvert.DbToType(this.CplPlanWorkplace, SourceFixUnitMode.NoPlanning); }
		}
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Zdroj operace
		/// </summary>
		internal SourceFixUnitMode SourceFixModeProfession
		{
			get { return EnumConvert.DbToType(this.CplPlanProfession, SourceFixUnitMode.NoPlanning); }
		}
		/// <summary>
		/// Režim fixace kapacit pro kapacitní zdroj = Zdroj operace
		/// </summary>
		internal SourceFixUnitMode SourceFixModeResource
		{
			get { return EnumConvert.DbToType(this.CplPlanResource, SourceFixUnitMode.NoPlanning); }
		}
        #endregion
    }
	#endregion
}
#endregion
